package com.enation.app.javashop.service.member.impl;

import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.enation.app.javashop.framework.auth.Token;
import com.enation.app.javashop.framework.cache.Cache;
import com.enation.app.javashop.framework.context.request.ThreadContextHolder;
import com.enation.app.javashop.framework.context.user.UserContext;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.logs.Logger;
import com.enation.app.javashop.framework.logs.LoggerFactory;
import com.enation.app.javashop.framework.rabbitmq.MessageSender;
import com.enation.app.javashop.framework.rabbitmq.MqMessage;
import com.enation.app.javashop.framework.security.TokenManager;
import com.enation.app.javashop.framework.security.model.Buyer;
import com.enation.app.javashop.framework.util.Base64;
import com.enation.app.javashop.framework.util.EmojiCharacterUtil;
import com.enation.app.javashop.framework.util.HttpUtils;
import com.enation.app.javashop.framework.util.StringUtil;
import com.enation.app.javashop.mapper.member.ConnectMapper;
import com.enation.app.javashop.mapper.member.MemberMapper;
import com.enation.app.javashop.model.base.CachePrefix;
import com.enation.app.javashop.model.base.message.MemberRegisterMsg;
import com.enation.app.javashop.model.base.rabbitmq.AmqpExchange;
import com.enation.app.javashop.model.errorcode.MemberErrorCode;
import com.enation.app.javashop.model.member.dos.ConnectDO;
import com.enation.app.javashop.model.member.dos.Member;
import com.enation.app.javashop.model.member.dto.WeChatMiniLoginDTO;
import com.enation.app.javashop.model.member.dto.WeChatUserDTO;
import com.enation.app.javashop.model.member.enums.ConnectTypeEnum;
import com.enation.app.javashop.model.member.vo.Auth2Token;
import com.enation.app.javashop.model.member.vo.MemberLoginMsg;
import com.enation.app.javashop.model.member.vo.MemberVO;
import com.enation.app.javashop.model.payment.enums.WechatTypeEnmu;
import com.enation.app.javashop.service.member.ConnectManager;
import com.enation.app.javashop.service.member.MemberManager;
import com.enation.app.javashop.service.member.WeChatLoginManager;
import com.enation.app.javashop.service.passport.signaturer.WechatSignaturer;
import com.enation.app.javashop.service.statistics.util.DateUtil;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;


/**
 * 微信统一登陆服务实现
 * @author cs
 * @since v1.0
 * @version 7.2.2
 * 2020/09/24
 */
@Service
public class WeChatLoginManagerImpl implements WeChatLoginManager {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private WechatSignaturer wechatSignaturer;
    @Autowired
    private ConnectMapper connectMapper;

    @Autowired
    private MemberManager memberManager;

    @Autowired
    private MemberMapper memberMapper;

    @Autowired
    private ConnectManager connectManager;

    @Autowired
    private Cache cache;

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private TokenManager tokenManager;

    private static final String WX_MINI_SESSIONKEY="{WX}{MINI}{SESSION_KEY}_";

    private static final Integer WX_TOKEN_VAILD_TIME_APP = 60*60*24*90;

    private static final Integer WX_TOKEN_VAILD_TIME_MINI = 60*60*24*2;


    @Override
    public String getLoginUrl(String redirectUri) {
        Map<String, String> connectConfig = wechatSignaturer.getConnectConfig(WechatTypeEnmu.WAP.name());
        String appId = connectConfig.get("app_id");
        StringBuffer loginBuffer = new StringBuffer("https://open.weixin.qq.com/connect/oauth2/authorize?");
        loginBuffer.append("appid=").append(appId);
        loginBuffer.append("&redirect_uri=").append(redirectUri);
        loginBuffer.append("&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect");
        return loginBuffer.toString();
    }

    @Override
    public JSONObject getAccessToken(String code) {
        Map<String, String> connectConfig = wechatSignaturer.getConnectConfig(WechatTypeEnmu.WAP.name());
        String appId = connectConfig.get("app_id");
        String secret = connectConfig.get("app_key");
        StringBuffer accessTokenBuffer = new StringBuffer("https://api.weixin.qq.com/sns/oauth2/access_token?");
        accessTokenBuffer.append("appid=").append(appId);
        accessTokenBuffer.append("&secret=").append(secret);
        accessTokenBuffer.append("&code=").append(code);
        accessTokenBuffer.append("&grant_type=authorization_code");
        String access_token_back = HttpUtils.doGet(accessTokenBuffer.toString(),"UTF-8", 1000, 1000);
        JSONObject jsonObject = JSONObject.fromObject(access_token_back);
        return jsonObject;
    }

    @Override
    public JSONObject getWechatInfo(String accessToken, String openId) {
        StringBuffer wechatInfoBuffer = new StringBuffer("https://api.weixin.qq.com/sns/userinfo?");
        wechatInfoBuffer.append("access_token=").append(accessToken);
        wechatInfoBuffer.append("&openid=").append(openId);
        wechatInfoBuffer.append("&lang=zh_CN");
        String user_info_back = HttpUtils.doGet(wechatInfoBuffer.toString(),"UTF-8", 1000, 1000);
        JSONObject user_info_json = JSONObject.fromObject(user_info_back);
        return user_info_json;
    }


    @Override
    public Map wxWapLogin(String code,String uuid) {
        JSONObject accessTokenJson = this.getAccessToken(code);
        String access_token = accessTokenJson.getString("access_token");
        String openid = accessTokenJson.getString("openid");
        JSONObject wechatInfoJson = this.getWechatInfo(access_token, openid);
        String unionid = wechatInfoJson.getString("unionid");
        if (StringUtil.isEmpty(unionid)){
            throw new ServiceException("403","请将公众号绑定到微信开放平台");
        }
        WeChatUserDTO weChatUserDTO = new WeChatUserDTO();
        weChatUserDTO.setOpenid(openid);
        weChatUserDTO.setUnionid(unionid);
        weChatUserDTO.setNickName(wechatInfoJson.getString("nickname"));
        weChatUserDTO.setHeadimgurl(wechatInfoJson.getString("headimgurl"));
        weChatUserDTO.setSex(wechatInfoJson.getInt("sex")==1?1:0);
        Map map = loginByUnionId(uuid, weChatUserDTO,null,null,ConnectTypeEnum.WECHAT_OPENID);
        return map;
    }

    @Override
    public Map wxAppLogin(String uuid,WeChatUserDTO weChatUserDTO) {
        JSONObject wechatInfoJson = this.getWechatInfo(weChatUserDTO.getAccessToken(), weChatUserDTO.getOpenid());
        //检测到access_token失效，利用refresh_token刷新access_token
        if (wechatInfoJson.get("errcode")!=null &&wechatInfoJson.getInt("errcode")==40014){
            Map<String, String> connectConfig = wechatSignaturer.getConnectConfig(WechatTypeEnmu.REACT.name());
            wechatInfoJson = this.getWechatInfo(wxRefreshToken(connectConfig.get("app_id"),weChatUserDTO.getRefreshToken()), weChatUserDTO.getOpenid());
        }
        weChatUserDTO.setHeadimgurl(wechatInfoJson.getString("headimgurl"));
        weChatUserDTO.setNickName(wechatInfoJson.getString("nickname"));
        if (wechatInfoJson.get("unionid")!=null){
            weChatUserDTO.setUnionid(wechatInfoJson.getString("unionid"));
        }
        weChatUserDTO.setSex(wechatInfoJson.getInt("sex")==1?1:0);
        return loginByUnionId(uuid,weChatUserDTO,WX_TOKEN_VAILD_TIME_APP,WX_TOKEN_VAILD_TIME_APP,ConnectTypeEnum.WECHAT_APP);
    }

    private String  wxRefreshToken(String appId,String refresh_token){
        StringBuffer accessTokenBuffer = new StringBuffer("https://api.weixin.qq.com/sns/oauth2/refresh_token?");
        accessTokenBuffer.append("appid=").append(appId);
        accessTokenBuffer.append("&grant_type=refresh_token");
        accessTokenBuffer.append("&refresh_token=").append(refresh_token);
        String refresh_token_back = HttpUtils.doGet(accessTokenBuffer.toString(),"UTF-8", 1000, 1000);
        JSONObject jsonObject = JSONObject.fromObject(refresh_token_back);
        if (jsonObject.get("access_token")==null){
            return null;
        }
        return jsonObject.getString("access_token");
    }

    @Override
    public Map miniLogin(WeChatMiniLoginDTO weChatMiniLoginDTO) {
        Map res = new HashMap(16);
        WeChatUserDTO weChatUserDTO = new WeChatUserDTO();
        String content = wxMiniAutoCode(weChatMiniLoginDTO.getCode());
        if (StringUtil.isEmpty(content)){
            res.put("autologin", "fail");
            res.put("reason", "auth_code_fail");
            return res;
        }
        JSONObject json = JSONObject.fromObject(content);
        String openid = json.getString("openid");
        weChatUserDTO.setOpenid(openid);
        // 获取会话密钥（session_key）
        String sessionKey = json.get("session_key").toString();
        cache.put(WX_MINI_SESSIONKEY+openid,sessionKey,60*60*24*2);
        //获取不到unionid
        JSONObject userInfoJson = connectManager.getUserInfo(weChatMiniLoginDTO.getEdata(), sessionKey, weChatMiniLoginDTO.getIv());
        if (userInfoJson.get("unionId")==null){
            res.put("autologin", "fail");
            res.put("reason", "fail_to_get_unionid");
            return res;
        }
        String unionId = userInfoJson.getString("unionId");
        weChatUserDTO.setUnionid(unionId);
        weChatUserDTO.setHeadimgurl(userInfoJson.get("avatarUrl")==null?null:userInfoJson.getString("avatarUrl"));
        weChatUserDTO.setNickName(userInfoJson.get("nickName")==null?null:userInfoJson.getString("nickName"));
        weChatUserDTO.setSex(userInfoJson.getInt("gender")==1?1:0);
        //存储auth2Token
        wxMiniSaveAuth2Token(weChatMiniLoginDTO.getUuid(),unionId,openid);
        return loginByUnionId(weChatMiniLoginDTO.getUuid(),weChatUserDTO,WX_TOKEN_VAILD_TIME_MINI,WX_TOKEN_VAILD_TIME_MINI,ConnectTypeEnum.WECHAT_MINI);
    }

    @Override
    public String getMiniOpenid(String code) {
        String content = wxMiniAutoCode(code);
        if (StringUtil.isEmpty(content)){
            return "";
        }
        JSONObject json = JSONObject.fromObject(content);
        String openid = json.getString("openid");
        return openid;
    }

    @Override
    public Map miniBindPhone(String encryptedData,String iv) {
        Map res = new HashMap(16);
        Long uid = UserContext.getBuyer().getUid();
        ConnectDO connectDO = connectMapper.selectOne(new QueryWrapper<ConnectDO>().eq("member_id", uid).eq("union_type", ConnectTypeEnum.WECHAT_MINI));
        if (null == connectDO){
            res.put("bindPhone","fail");
            res.put("reason","fail_to_get_openid");
            return res;
        }
        String openId = connectDO.getUnionId();
        String sessionKey = (String) cache.get(WX_MINI_SESSIONKEY + openId);
        if (StringUtil.isEmpty(sessionKey)){
            res.put("bindPhone","fail");
            res.put("reason","fail_to_get_sessionKey");
            return res;
        }
        try {
            String decryptPhoneStr = decrypt(Base64.decode(sessionKey),Base64.decode(iv),Base64.decode(encryptedData));
            JSONObject jsonObject = JSONObject.fromObject(decryptPhoneStr);
            if (jsonObject.get("phoneNumber")!=null){
                String mobile = jsonObject.getString("phoneNumber");
                memberMapper.update(null,new UpdateWrapper<Member>().set("mobile",mobile).eq("member_id",uid));
                res.put("bindPhone","success");
                res.put("phone",mobile);
                return res;
            }else{
                res.put("bindPhone","fail");
                res.put("reason","fail_to_find_mobile_from_decrypt_data");
                return res;
            }
        } catch (Exception e) {
            e.printStackTrace();
            res.put("bindPhone","fail");
            res.put("reason",e.getMessage());
            return res;
        }
    }

    public static String decrypt(byte[] key, byte[] iv, byte[] encData) throws Exception {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        //解析解密后的字符串
        return new String(cipher.doFinal(encData),"UTF-8");
    }

    /**
     * 存储auth2Token
     * @param uuid
     * @param unionid
     * @param openid
     */
    private void wxMiniSaveAuth2Token(String uuid,String unionid,String openid) {
        //存储uuid和unionId的关系
        Auth2Token auth2Token = new Auth2Token();
        auth2Token.setType(ConnectTypeEnum.WECHAT_MINI.value());
        auth2Token.setUnionid(unionid);
        //openid用于注册绑定时获取登录的微信的信息
        auth2Token.setOpneId(openid);
        logger.debug("微信小程序登录openId为：" + openid);
        cache.put(CachePrefix.CONNECT_LOGIN.getPrefix() + uuid, auth2Token);
    }

    private String wxMiniAutoCode(String code) {
        Map<String, String> connectConfig = wechatSignaturer.getConnectConfig(WechatTypeEnmu.MINI.name());
        String appId = connectConfig.get("app_id");
        String appKey = connectConfig.get("app_key");
        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + appId + "&" +
                "secret=" + appKey + "&" +
                "js_code=" + code + "&" +
                "grant_type=authorization_code";
        return HttpUtils.doGet(url, "UTF-8", 100, 1000);
    }

    /**
     * 根据UnionId登陆
     * @param weChatUserDTO
     * @return
     */
    private Map loginByUnionId(String uuid,WeChatUserDTO weChatUserDTO,Integer tokenOutTime,Integer refreshTokenOutTime,ConnectTypeEnum loginType){
        Map res = new HashMap(16);
        //通过unionid查找会员(es_connect表)
        ConnectDO connectDO= findMemberByUnionId(weChatUserDTO.getUnionid());
        Member member = null;
        if(connectDO==null){
            //没找到注册一个
            member = registerBy(weChatUserDTO,loginType);
        }else{
            member=findMemberById(connectDO.getMemberId(),weChatUserDTO.getOpenid(),loginType);
        }
        //存储uuid和unionId的关系
        Auth2Token auth2Token = new Auth2Token();
        auth2Token.setType(loginType.value());
        auth2Token.setUnionid(weChatUserDTO.getUnionid());
        //openid用于注册绑定时获取登录的微信的信息
        auth2Token.setOpneId(weChatUserDTO.getOpenid());
        logger.debug("微信登录openId为：" + weChatUserDTO.getOpenid());
        cache.put(CachePrefix.CONNECT_LOGIN.getPrefix() + uuid, auth2Token);
        MemberVO memberVO = this.connectWeChatLoginHandle(member, uuid,tokenOutTime,refreshTokenOutTime);
        res.put("access_token", memberVO.getAccessToken());
        res.put("refresh_token", memberVO.getRefreshToken());
        res.put("uid", memberVO.getUid());
        return res;
    }



    /**
     * 生成member的token
     *
     * @param member
     * @param uuid
     * @return
     */
    private MemberVO convertWechatMember(Member member, String uuid,Integer tokenOutTime,Integer refreshTokenOutTime) {
        //校验当前账号是否被禁用
        if (!member.getDisabled().equals(0)) {
            throw new ServiceException(MemberErrorCode.E107.code(), "当前账号已经禁用，请联系管理员");
        }

        //新建买家用户角色对象
        Buyer buyer = new Buyer();
        //设置用户ID
        buyer.setUid(member.getMemberId());
        //设置用户名称
        buyer.setUsername(member.getUname());
        //设置uuid
        buyer.setUuid(uuid);
        //创建Token
        Token token = tokenManager.create(buyer,tokenOutTime,refreshTokenOutTime);
        //获取访问Token
        String accessToken = token.getAccessToken();
        //获取刷新Token
        String refreshToken = token.getRefreshToken();
        //组织返回数据
        MemberVO memberVO = new MemberVO(member, accessToken, refreshToken);
        return memberVO;
    }


    public MemberVO connectWeChatLoginHandle(Member member, String uuid,Integer tokenOutTime,Integer refreshTokenOutTime) {
        //初始化会员信息
        MemberVO memberVO = this.convertWechatMember(member, uuid,tokenOutTime,refreshTokenOutTime);
        //发送登录消息
        this.sendMessage(member, 1);
        return memberVO;
    }

    /**
     * 发送登录消息
     *
     * @param member
     * @param memberOrSeller
     */
    private void sendMessage(Member member, Integer memberOrSeller) {
        MemberLoginMsg loginMsg = new MemberLoginMsg();
        loginMsg.setLastLoginTime(member.getLastLogin());
        loginMsg.setMemberId(member.getMemberId());
        loginMsg.setMemberOrSeller(memberOrSeller);
        this.messageSender.send(new MqMessage(AmqpExchange.MEMEBER_LOGIN, AmqpExchange.MEMEBER_LOGIN + "_ROUTING", loginMsg));
    }


    /**
     * 根据用户id查询用户信息
     * @param memberId
     * @param openid
     * @param loginType
     * @return
     */
    private Member findMemberById(Long memberId, String openid, ConnectTypeEnum loginType) {
        Member member = memberManager.getModel(memberId);
        //查看当前登陆终端该账户openid是否已记录，如果未记录则新增记录
        ConnectDO aDo = connectMapper.selectOne(new QueryWrapper<ConnectDO>().eq("member_id", member.getMemberId()).eq("union_type", loginType.value()));
        if (aDo==null){
            ConnectDO connect = new ConnectDO();
            connect.setMemberId(member.getMemberId());
            connect.setUnionId(openid);
            connect.setUnionType(loginType.value());
            connectMapper.insert(connect);
        }
        return member;
    }

    private MemberVO copyMemberToVO(Member member) {
        MemberVO memberVO = new MemberVO();
        memberVO.setUid(member.getMemberId());
        memberVO.setFace(member.getFace());
        memberVO.setNickname(member.getNickname());
        memberVO.setUsername(member.getUname());
        return memberVO;
    }

    /**
     * 注册
     * @param weChatUserDTO
     * @return
     */
    private Member  registerBy(WeChatUserDTO weChatUserDTO,ConnectTypeEnum loginType) {
        String usernamePrefix = "m_";
        String usernameSuffix = UUID.fastUUID().toString(true).substring(0,9);
        String username = usernamePrefix+usernameSuffix;
        Member memberByName = memberManager.getMemberByName(username);
        while (memberByName!=null){
            usernameSuffix = UUID.fastUUID().toString(true).substring(0,9);
            username = usernamePrefix+usernameSuffix;
            memberByName = memberManager.getMemberByName(username);
        }
        Member member = new Member();
        member.setUname(username);
        member.setSex(weChatUserDTO.getSex());
        long dateline = DateUtil.getDateline();
        member.setCreateTime(dateline);
        member.setFace(weChatUserDTO.getHeadimgurl());
        member.setRegisterIp(ThreadContextHolder.getHttpRequest().getRemoteAddr());
        member.setLoginCount(1);
        member.setLastLogin(dateline);
        member.setDisabled(0);
        String  nickname = weChatUserDTO.getNickName();
        nickname = StringUtil.isEmpty(nickname)?username: EmojiCharacterUtil.encode(nickname);
        member.setNickname(nickname);
        //设置会员等级积分为0
        member.setGradePoint(0L);
        //设置会员消费积分为0
        member.setConsumPoint(0L);
        //设置会员是否开启店铺 0：否，1：是
        member.setHaveShop(0);
        //设置会员是否完善了个人信息 0：否，1：是
        member.setInfoFull(0);
        memberMapper.insert(member);
        addConnect(weChatUserDTO, member,loginType);
        //组织数据结构发送会员注册消息
        MemberRegisterMsg memberRegisterMsg = new MemberRegisterMsg();
        memberRegisterMsg.setMember(member);
        memberRegisterMsg.setUuid(ThreadContextHolder.getHttpRequest().getHeader("uuid"));
        this.messageSender.send(new MqMessage(AmqpExchange.MEMEBER_REGISTER, AmqpExchange.MEMEBER_REGISTER + "_ROUTING", memberRegisterMsg));
        return member;
    }

    private void addConnect(WeChatUserDTO weChatUserDTO, Member member, ConnectTypeEnum loginType) {
        //写入UnionId
        ConnectDO connect = new ConnectDO();
        connect.setMemberId(member.getMemberId());
        connect.setUnionType(ConnectTypeEnum.WECHAT.value());
        connect.setUnionId(weChatUserDTO.getUnionid());
        connectMapper.insert(connect);
        //写入openId
        connect = new ConnectDO();
        connect.setMemberId(member.getMemberId());
        connect.setUnionType(loginType.value());
        connect.setUnionId(weChatUserDTO.getOpenid());
        connectMapper.insert(connect);
    }

    /**
     * 根据unionId查询connect
     * @param unionId
     * @return
     */
    private ConnectDO findMemberByUnionId(String unionId) {
        return connectMapper.selectOne(new QueryWrapper<ConnectDO>().eq("union_id",unionId).eq("union_type", ConnectTypeEnum.WECHAT));
    }
}
